package com.astrocsr.business;

import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.astrocsr.EbppException;
import com.astrocsr.common.Constants;
import com.astrocsr.common.util.ConverterUtil;
import com.astrocsr.common.util.DateUtil;
import com.astrocsr.common.util.PDFGenerator;
import com.astrocsr.domain.bean.PDFData;
import com.astrocsr.domain.bean.PDFStatement;
import com.astrocsr.domain.bean.PDFTransaction;
import com.astrocsr.domain.bean.TblAccount;
import com.astrocsr.domain.bean.TblGroupAccount;
import com.astrocsr.domain.condition.AccountCondition;
import com.astrocsr.domain.db.PagingInfo;
import com.astrocsr.domain.db.SearchResult;
import com.astrocsr.domain.web.AccountInfo;
import com.astrocsr.domain.web.BillInfo;
import com.astrocsr.domain.web.GroupAccount;
import com.checkfree.isolutions.customer.persist.Account;
import com.checkfree.isolutions.customer.persist.AccountHomeImpl;
import com.checkfree.isolutions.customer.persist.Merchant;
import com.checkfree.isolutions.customer.persist.MerchantHomeImpl;
import com.checkfree.isolutions.persist.PersistServer;
import com.checkfree.isolutions.statement.FieldValue;
import com.checkfree.isolutions.statement.StatementSection;
import com.checkfree.isolutions.statement.persist.Statement;
import com.checkfree.isolutions.statement.persist.StatementHomeImpl;
import com.itextpdf.text.html.HtmlEncoder;

@Service("statementManager")
@Transactional(value = "transactionManager", propagation = Propagation.REQUIRED)
public class StatementManager extends BaseManager {

	/**
	 * Get a list of statements by ACCOUNT No and Date Range
	 */
	@SuppressWarnings("unchecked")
	public Vector<Statement> getStatements(String accountNo, Date startDate,
			Date endDate) throws Exception {

		Vector<Statement> vecStatements = null;
		try {

			Merchant theMerchant = MerchantHomeImpl.singleton().findByName(
					Constants.MERCHANT);
			if (theMerchant == null) {
				throw new EbppException(
						"unable to obtain Persist Merchant object");
			}
			Account account = AccountHomeImpl.singleton()
					.findByExternalKeyAndMerchant(accountNo, theMerchant);
			if (account == null) {
				//throw new EbppException("unable to obtain Persist Account object");
				return null;
			} else {
				vecStatements = StatementHomeImpl.singleton().findByAccount(
						account, startDate, endDate);
			}

		} catch (Exception e) {
			throw e;
		} catch (Throwable e) {
			new Exception(e);
		}
		return vecStatements;
	}

	/**
	 * Get a statement by ACCOUNT NO and STATEMENT DATE
	 */
	public Statement getStatement(String accountNo, Date statementDate) throws Exception {

		Statement statement = null;
		try {

			Merchant theMerchant = MerchantHomeImpl.singleton().findByName(
					Constants.MERCHANT);
			if (theMerchant == null) {
				throw new EbppException(
						"unable to obtain Persist Merchant object");
			}
			Account account = AccountHomeImpl.singleton()
					.findByExternalKeyAndMerchant(accountNo, theMerchant);
			if (account == null) {
				//throw new EbppException("unable to obtain Persist Account object");
				return null;
			} else {

				statement = StatementHomeImpl.singleton().findByAccountAndDate(account, statementDate);
			}

		} catch (Exception e) {
			throw e;
		} catch (Throwable e) {
			new Exception(e);
		}
		return statement;
	}
	
	/**
	 * Search Statement Date in Special Month
	 */
	public List<BillInfo> searchStatementDateInMonth(List<GroupAccount> groupAccountList, Date date) throws Exception {
		boolean txStartedHere = false;
		List<BillInfo>   billInfoList = null;
		try {
			List<String> strDateList = null;
			HashMap<String, String> mapDate = new HashMap<String, String>(); 

			PersistServer.activate(Constants.MERCHANT);
			txStartedHere = startTransactionIfNeeded();

			Date beginingOfMonth = DateUtil.getBeginningOfMonth(date);
			Date endOfMonth 	 = DateUtil.getEndOfMonth(date);

			if (groupAccountList != null && groupAccountList.size() > 0) {
				for (int i=0; i<groupAccountList.size(); i++) {
					Vector<Statement> statementVector = getStatements(groupAccountList.get(i).getAccountNo(), beginingOfMonth, endOfMonth);
					Statement statement 		 = null;
					
					if (statementVector != null && statementVector.size() > 0) {
						int cntVT = statementVector.size();
						for (int j = 0; j < cntVT; j++) {
							statement = statementVector.get(j);
							Date t_stmtDate = statement.getDate();
							String strStmtDate = DateUtil.formatUniversal(t_stmtDate);
							if (!mapDate.containsKey(strStmtDate)) {
								mapDate.put(strStmtDate, strStmtDate);
							}
						}
					}
				}
			
				if (mapDate.size() > 0) {
					strDateList  = new ArrayList<String>();
					billInfoList = new ArrayList<BillInfo>();
					Iterator<String> itr = mapDate.keySet().iterator();
					while (itr.hasNext()) {
						String key = (String)itr.next();
						strDateList.add(key);
					}
					java.util.Collections.sort(strDateList);
					java.util.Collections.reverse(strDateList);
					BillInfo item = null;
					for (int i = 0; i < strDateList.size(); i++) {
						item = new BillInfo();
						item.setStmtdate(DateUtil.formatLocalSlash(DateUtil.createDateUniversal(strDateList.get(i))));
						billInfoList.add(item);
					}
				}
			}
		} catch (Exception e) {
			throw e;
		} catch (Throwable e) {
			throw new Exception(e);
		} finally {
			commitTransactionIfNeeded(txStartedHere);
			PersistServer.deactivate();
		}
		return billInfoList;
	}
	
	
	/**
	 * Generate PDF
	 */
	public static void main(String[] args) throws Exception {
		StatementManager sm = new StatementManager();
		String groupCode = Constants.GROUP_ASTRO_ADMIN;
		Date statementDate = DateUtil.createDateLocalSlash("11/01/2014");
		final String resourcePath = "D:/workspace/hlbb_csr/src/main/webapp/resource/";

		byte[] pdfBytes = sm.getPDFData(groupCode, statementDate, resourcePath);
		
		FileOutputStream fos = new FileOutputStream("C:/tmp/100_1000.pdf");
		fos.write(pdfBytes);
		fos.close();
	}
	
	public List<GroupAccount> listGroupAccountByGroupCode_ForPDF_TEST(Long userId) throws Exception {
		List<GroupAccount> groupAccountList = new ArrayList<GroupAccount>();
		String[] accNoArr = {"080999246-2","081168292-4","081605223-2","081843909-9","083593945-5","089039541-0"};
		GroupAccount userAccount = null;
		for (int i = 0; i < accNoArr.length; i++) {
			userAccount = new GroupAccount();
			userAccount.setAccountNo(accNoArr[i]);
			groupAccountList.add(userAccount);
		}
		return groupAccountList;
	}
	
	public SearchResult<GroupAccount> listGroupAccountByGroupCode(String groupCode, AccountInfo accountInfo, PagingInfo pagingInfo) throws Exception {
		
		// prepare condition
		AccountCondition condition = new AccountCondition(groupCode);
		if (accountInfo != null) {
			condition.setAccountNo(accountInfo.getAccountNo());
		}
		
		SearchResult<TblGroupAccount> beanList = null;
		SearchResult<GroupAccount> 	  infoList = null;

		if (Constants.GROUP_ASTRO_ADMIN.equals(groupCode)) {
			infoList = listAllAccount(condition, pagingInfo);
		} else {
			beanList = getGroupAccountDAO().listByGroup(condition, pagingInfo);

			if (beanList != null && beanList.size() > 0) {
				infoList = new SearchResult<GroupAccount>();
				GroupAccount info = null;
				for (TblGroupAccount bean : beanList) {
					info = new GroupAccount();
					BeanUtils.copyProperties(bean, info);
					infoList.add(info);
				}
				
				infoList.setPagingInfo(beanList.getPagingInfo());
			}
		}
		
		return infoList;
	}
	
	public List<GroupAccount> listGroupAccountByAccountNo(String accountNo) throws Exception {
		List<TblGroupAccount> beanList = getGroupAccountDAO().listByAccount(accountNo);
		List<GroupAccount> 	  infoList = null;
		
		if (beanList != null && beanList.size() > 0) {
			infoList = new ArrayList<GroupAccount>();
			GroupAccount info = null;
			for (TblGroupAccount bean : beanList) {
				info = new GroupAccount();
				BeanUtils.copyProperties(bean, info);
				infoList.add(info);
			}
		}
		
		return infoList;
	}
	
	public SearchResult<GroupAccount> listAllAccount(AccountCondition condition, PagingInfo pagingInfo) throws Exception {
		SearchResult<TblAccount> beanList = getGroupAccountDAO().listAllAccount(condition, pagingInfo);
		SearchResult<GroupAccount> 	  infoList = null;
		
		if (beanList != null && beanList.size() > 0) {
			infoList = new SearchResult<GroupAccount>();
			GroupAccount info = null;
			for (TblAccount bean : beanList) {

				info = new GroupAccount();
				info.setAccountNo(bean.getAccountNo());
				info.setCreatedDate(DateUtil.createDateFromJulianDate(bean.getCreatedDate()));

				infoList.add(info);
			}
			infoList.setPagingInfo(beanList.getPagingInfo());
		}
		
		return infoList;
	}
	
	public byte[] getPDFData(String groupCode, Date statementDate, final String resourcePath) throws Exception{
		boolean txStartedHere = false;
		try {
			PersistServer.activate(Constants.MERCHANT);
			txStartedHere = startTransactionIfNeeded();
			
			//List<UserAccount> groupAccounts = listGroupAccount_ForPDF_TEST(userId); // To Test
			List<GroupAccount> groupAccounts = listGroupAccountByGroupCode(groupCode, null, null);

			PDFData pdfData = new PDFData();
			for (GroupAccount groupAccount : groupAccounts) {
				PDFStatement statement = getPDFStatement(groupAccount.getAccountNo(), statementDate);
				
				if(pdfData.getSymbol() == null || pdfData.getSymbol().length() == 0) {
					pdfData.setSymbol(getSymbol(statement.getTotalAmountDueWithSymbol()));
				}
				
				if(pdfData.getAddresses().isEmpty()){
					pdfData.setAddresses(statement.getAddresses());
				}
				
				if(pdfData.getStatementDate() == null || pdfData.getStatementDate().isEmpty()){
					pdfData.setStatementDate(statement.getStatementDate());
				}
				
				pdfData.setDueAmnt(pdfData.getDueAmnt().add(ConverterUtil.MoneyToBigDecimal(statement.getDueAmntWithSymbol())));;
				pdfData.setTotalAmountDue(pdfData.getTotalAmountDue().add(ConverterUtil.MoneyToBigDecimal(statement.getTotalAmountDueWithSymbol())));
				pdfData.setPaymentAdjustment(pdfData.getPaymentAdjustment().add(ConverterUtil.MoneyToBigDecimal(statement.getPaymentAdjustmentWithSymbol())));
				pdfData.setPreviousBalance(pdfData.getPreviousBalance().add(ConverterUtil.MoneyToBigDecimal(statement.getPreviousBalanceWithSymbol())));
				pdfData.setUnpaid(pdfData.getUnpaid().add(ConverterUtil.MoneyToBigDecimal(statement.getUnpaidWithSymbol())));
				pdfData.setNewCharges(pdfData.getNewCharges().add(ConverterUtil.MoneyToBigDecimal(statement.getNewChargeWithSymbol())));
				
				pdfData.setTotalAmountCharge(pdfData.getTotalAmountCharge().add(statement.getTotalAmountCharge()));
				pdfData.setTotalGST(pdfData.getTotalGST().add(statement.getTotalGST()));
				pdfData.setTotalAfterGST(pdfData.getTotalAfterGST().add(statement.getTotalAfterGST()));
				
				if(pdfData.getCustName() == null || pdfData.getCustName().isEmpty()){
					pdfData.setCustName(statement.getCustName());
				}
				
				if(pdfData.getDueDate() == null || pdfData.getDueDate().isEmpty()){
					pdfData.setDueDate(statement.getDueDate());
				}
				
				pdfData.getStatements().add(statement);
			}
			return genPdf(pdfData, resourcePath);
		}catch (Exception ex){
			throw ex;
		} finally {
			commitTransactionIfNeeded(txStartedHere);
			PersistServer.deactivate();
		}
		
	}

	private PDFStatement getPDFStatement(String accountNo, Date statementDate) throws Exception {
		PDFStatement statement = new PDFStatement();
			Statement cfiStmt = getStatement(accountNo, statementDate);
			StatementSection rootSection = null;

			if (cfiStmt != null && cfiStmt.getRootSection() != null) {
				rootSection = cfiStmt.getRootSection();
				statement.setCustName(HtmlEncoder.encode(getFieldText(rootSection,"custname")));
				statement.setAccountNo(getFieldText(rootSection,"AccountNumber"));
				statement.setDueDate(getFieldText(rootSection,"duedate"));
				statement.setStatementDate(getFieldText(rootSection,"StatementDate"));
				
				StatementSection detailStatement = rootSection.getSection("DetailsSection");
				StatementSection summaryOfStatement = detailStatement.getSection("SummaryOfChargesSection");

				statement.setDueAmntWithSymbol(getFieldText(rootSection, "dueamt"));
				statement.setPreviousBalanceWithSymbol(getFieldText(summaryOfStatement,"PreviousMonthBalance"));
				statement.setPaymentAdjustmentWithSymbol(getFieldText(summaryOfStatement,"PayOrAdjustments"));
				statement.setUnpaidWithSymbol(getFieldText(summaryOfStatement, "Unpaid"));
				statement.setNewChargeWithSymbol(getFieldText(summaryOfStatement, "NewCharges"));
				statement.setTotalAmountDueWithSymbol(getFieldText(summaryOfStatement, "TotalAmountDue"));

				StatementSection addstate = rootSection.getSection("AddressSection");
				if(addstate != null){
					int addressSectionCount = (int) addstate.getSectionCount("RepeatSection");
					for (int i = 0; i < addressSectionCount; i++) {
						StatementSection addressRepeatSection = addstate.getRepeatSection("RepeatSection", i);
						String address = getField(addressRepeatSection, "Address");
						if(address != null && !address.isEmpty()){
							statement.getAddresses().add(HtmlEncoder.encode(address));
						}
					}
				}
				
				List<PDFTransaction> transactions = new ArrayList<PDFTransaction>();
				statement.setTransactions(transactions);
				
				if (detailStatement != null) {
					int chargesRepeatSectionCount = (int) detailStatement.getSectionCount("ChargesRepeatSection");
					StatementSection chargesRepeatSection = detailStatement.getRepeatSection("ChargesRepeatSection", 0);
					String ignoreItem = "Smart Card No. :";
					String smartCardNo = "";
					for (int i = 0; i < chargesRepeatSectionCount; i++) {
						PDFTransaction trans = new PDFTransaction();
						chargesRepeatSection = detailStatement.getRepeatSection("ChargesRepeatSection", i);
						trans.setItem(HtmlEncoder.encode(getField(chargesRepeatSection, "Item")));
						trans.setSmartCardNo(getField(chargesRepeatSection,"SmartCardNum"));
						trans.setAmountCharge(ConverterUtil.ToBigDecimal(getField(chargesRepeatSection,"AmountCharged")));
						trans.setProrationPeriodFrom(getField(chargesRepeatSection,"FromDt"));
						trans.setProrationPeriodTo(getField(chargesRepeatSection, "ToDt"));
						
						if(trans.getAmountCharge().compareTo(BigDecimal.ZERO) > 0){
							trans.setGst(trans.getAmountCharge().multiply(new BigDecimal(0.06)).setScale(2, BigDecimal.ROUND_HALF_DOWN));
						}
						
						trans.setTotal(trans.getAmountCharge().add(trans.getGst()));
						
						statement.setTotalAfterGST(statement.getTotalAfterGST().add(trans.getTotal()));
						
						statement.setTotalAmountCharge(statement.getTotalAmountCharge().add(trans.getAmountCharge()));
						
						statement.setTotalGST(statement.getTotalGST().add(trans.getGst()));
						
						if(trans.getItem().equals(ignoreItem)){
							smartCardNo = trans.getSmartCardNo();
							continue;
						}
						
						trans.setSmartCardNo(smartCardNo);
						
						transactions.add(trans);	
					}
					
				}
			}
		return statement;
		
	}

	/**
	 * Generate PDF report
	 */
	private byte[] genPdf(PDFData pdfData, final String resourcePath) throws Exception {
		byte[] encoded = null;
		try {
			Velocity.setProperty("resource.loader", "class");
			Velocity.setProperty("class.resource.loader.class",
					"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
			Velocity.init();
			Template t = Velocity.getTemplate("template/report-template.html");
			VelocityContext ctx = new VelocityContext();

			ctx.put("pdfData", pdfData);

			Writer writer = new StringWriter();
			t.merge(ctx, writer);
			
			byte[] barray = writer.toString().getBytes("UTF-8");
			InputStream is = new ByteArrayInputStream(barray); 

			encoded = PDFGenerator.generatePdfFile(is,resourcePath);

		} catch (Exception e) {
			throw e;
		}
		
		return encoded;
	}

	private String getFieldText(StatementSection statementSection, String name) {
		return statementSection.getFieldText(name);
	}
	
	private String getField(StatementSection statementSection, String name) {
		FieldValue fieldValue = statementSection.getField(name);
		if (fieldValue == null) return "";
		else return fieldValue.asString();
	}
	
	private String getSymbol(String money){
		if (money == null || money.length() == 0) {
			return "";
		}
		Pattern pattern = Pattern.compile("^[^\\d]\\w");
		Matcher matcher = pattern.matcher(money);
		if(matcher.find()) {
			return matcher.group();
		}
		else {
			return "";
		}
	}
	
}